﻿using System;
using System.Linq;
using FizzBuzzLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FizzBuzzLibTest
{
    /// <summary>
    /// Unit tests for the FizzBuzz library
    /// </summary>
    [TestClass]
    public class LibTest
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        /// <summary>
        /// Calls the action from a try block and catches the exception it throws.
        /// Asserts if exception is of wrong type or if no exception is thrown.
        /// </summary>
        /// <typeparam name="ExpectedType">Exception or a type derived from it.</typeparam>
        /// <param name="action">Action that should throw an exception.</param>
        /// <remarks>
        /// The ExpectedException attribute can't distinguish between
        /// an exception thrown in the code under test and the test itself.
        /// Using this function narrows the scope where an exception is expected.
        /// </remarks>
        void ExpectException<ExpectedType>(Action action) where ExpectedType : Exception
        {
            try
            {
                action();
            }
            catch (Exception exception)
            {
                Assert.AreEqual(typeof(ExpectedType), exception.GetType(), "Wrong type of exception was thrown.");
                return;
            }

            Assert.Fail("Expected an exception, but no exception was thrown.");
        }

        [TestMethod]
        public void TheHappyPath()
        {
            int start = 1;
            int count = 20;

            string[] results = FizzBuzz.Results(start, count).ToArray();

            CollectionAssert.AllItemsAreNotNull(results);
            CollectionAssert.AreEqual("1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz".Split(' '), results);
        }

        [TestMethod]
        public void CustomOutputStrings()
        {
            int start = 1;
            int count = 20;
            string fizz = "by3";
            string buzz = "by5";
            string fizzBuzz = "by3&by5";

            string[] results = FizzBuzz.Results(start, count, fizz, buzz, fizzBuzz).ToArray();

            CollectionAssert.AllItemsAreNotNull(results);
            CollectionAssert.AreEqual("1 2 by3 4 by5 by3 7 8 by3 by5 11 by3 13 14 by3&by5 16 17 by3 19 by5".Split(' '), results);
        }

        [TestMethod]
        public void TopOfRange()
        {
            int count = 100;
            int start = int.MaxValue - count;

            var results = FizzBuzz.Results(start, count);

            Assert.AreEqual(count, results.Count());
        }

        [TestMethod]
        public void JustAboveTopOfRange()
        {
            int count = 100;
            int start = int.MaxValue - count + 1;

            var results = FizzBuzz.Results(start, count);

            ExpectException<OverflowException>(() => results.Count());
        }

        [TestMethod]
        public void Badcount()
        {
            int count = -1;
            int start = 1;

            ExpectException<ArgumentOutOfRangeException>(() => FizzBuzz.Results(start, count).First());
        }

        [TestMethod]
        public void NullFizz()
        {
            ExpectException<ArgumentNullException>(() => FizzBuzz.Results(1, 100, null, "Buzz", "FizzBuzz").First());
        }

        [TestMethod]
        public void NullBuzz()
        {
            ExpectException<ArgumentNullException>(() => FizzBuzz.Results(1, 100, "Fizz", null, "FizzBuzz").First());
        }

        [TestMethod]
        public void NullFizzBuzz()
        {
            ExpectException<ArgumentNullException>(() => FizzBuzz.Results(1, 100, "Fizz", "Buzz", null).First());
        }
    }
}
